# Entities

import index from '!!raw-loader!@site/docs/examples/entities.ex';
import { LiveDemo } from '@site/components/LiveDemo';

This feature enables the store to act as an entities store. You can think of an `entities` state as a table in a database,
where each table represents a flat collection of similar entities. Elf's entities state simplifies the process, giving you
everything you need to manage it.

First, you need to install the package by using the CLI command `elf-cli install` and selecting the entities package,
or via npm:

```bash
npm i @ngneat/elf-entities
```

To use this feature, provide the `withEntities` props factory function in the `createStore` call:

```ts
import { createStore } from '@ngneat/elf';
import { withEntities } from '@ngneat/elf-entities';

interface Todo {
  id: number;
  label: string;
}

const todosStore = createStore({ name: 'todos' }, withEntities<Todo>());
```

<LiveDemo src={index} packages={['entities']} />
<br />

This will allow you to use the following ready-made mutations and queries:

## Queries

### `selectAllEntities`

Select the entire store's entity collection:

```ts
import { selectAllEntities } from '@ngneat/elf-entities';

const todos$ = todosStore.pipe(selectAllEntities());
```

### `selectAllEntitiesApply`

Select the entire store's entity collection, and apply a `filter/map`:

```ts
import { selectAllEntitiesApply } from '@ngneat/elf-entities';

const titles$ = todosStore.pipe(
  selectAllEntitiesApply({
    mapEntity: (e) => e.title,
    filterEntity: (e) => e.completed,
  }),
);
```

In the above example, it'll first apply the `filter` and then the `map` function.

### `getAllEntitiesApply`

Get the entire store's entity collection, and apply a `filter/map`:

```ts
import { getAllEntitiesApply } from '@ngneat/elf-entities';

const titles = todosStore.query(
  getAllEntitiesApply({
    mapEntity: (e) => e.title,
    filterEntity: (e) => e.completed,
  }),
);
```

### `selectEntities`

Select the entire store's entity collection as object:

```ts
import { selectEntities } from '@ngneat/elf-entities';

const todos$ = todosStore.pipe(selectEntities());
```

### `selectEntity`

Select an entity or a slice of an entity:

```ts
import { selectEntity } from '@ngneat/elf-entities';

const todo$ = todosStore.pipe(selectEntity(id));
const title$ = todosStore.pipe(selectEntity(id, { pluck: 'title' }));
const title$ = todosStore.pipe(selectEntity(id, { pluck: (e) => e.title }));
```

### `selectEntityByPredicate`

Select an entity from the store by predicate:

```ts
import { selectEntityByPredicate } from '@ngneat/elf-entities';

const todo$ = todosStore.pipe(
  selectEntityByPredicate(({ completed }) => !completed),
);
const title$ = todosStore.pipe(
  selectEntityByPredicate(({ completed }) => !completed, {
    pluck: 'title',
    idKey: '_id',
  }),
);
const title$ = todosStore.pipe(
  selectEntityByPredicate(({ completed }) => !completed, {
    pluck: (e) => e.title,
    idKey: '_id',
  }),
);
```

### `selectMany`

Select multiple entities from the store:

```ts
import { selectMany } from '@ngneat/elf-entities';

const todos$ = todosStore.pipe(selectMany([id, id]));
const titles$ = todosStore.pipe(selectMany(id, { pluck: 'title' }));
const titles$ = todosStore.pipe(selectMany(id, { pluck: (e) => e.title }));
```

### `selectManyByPredicate`

Select multiple entities from the store by predicate:

```ts
import { selectManyByPredicate } from '@ngneat/elf-entities';

const todos$ = todosStore.pipe(
  selectManyByPredicate((entity) => entity.completed === false),
);
const titles$ = todosStore.pipe(
  selectManyByPredicate((entity) => entity.completed === false, {
    pluck: 'title',
  }),
);
const titles$ = todosStore.pipe(
  selectManyByPredicate((entity) => entity.completed === false, {
    pluck: (e) => e.title,
  }),
);
```

### `selectFirst`

Select the first entity from the store:

```ts
import { selectFirst } from '@ngneat/elf-entities';

const first$ = todosStore.pipe(selectFirst());
```

### `selectLast`

Select the last entity from the store:

```ts
import { selectLast } from '@ngneat/elf-entities';

const last$ = todosStore.pipe(selectLast());
```

### `selectEntitiesCount`

Select the store's entity collection size:

```ts
import { selectEntitiesCount } from '@ngneat/elf-entities';

const count$ = todosStore.pipe(selectEntitiesCount());
```

### `selectEntitiesCountByPredicate`

Select the store's entity collection size:

```ts
import { selectEntitiesCountByPredicate } from '@ngneat/elf-entities';

const count$ = todosStore.pipe(
  selectEntitiesCountByPredicate((entity) => entity.completed),
);
```

### `getAllEntities`

Get the entity collection:

```ts
import { getAllEntities } from '@ngneat/elf-entities';

const todos = todosStore.query(getAllEntities());
```

### `getEntitiesIds`

Get the entities ids:

```ts
import { getEntitiesIds } from '@ngneat/elf-entities';

const todosIds = todosStore.query(getEntitiesIds());
```

### `getEntity`

Get an entity by id:

```ts
import { getEntity } from '@ngneat/elf-entities';

const todo = todosStore.query(getEntity(id));
```

### `getEntityByPredicate`

Get first entity from the store by predicate:

```ts
import { getEntityByPredicate } from '@ngneat/elf-entities';

const todo = todosStore.query(
  getEntityByPredicate(({ title }) => title === 'Elf'),
);
```

### `hasEntity`

Returns whether an entity exists:

```ts
import { hasEntity } from '@ngneat/elf-entities';

if (todosStore.query(hasEntity(id))) {
}
```

### `getEntitiesCount`

Get the store's entity collection size:

```ts
import { getEntitiesCount } from '@ngneat/elf-entities';

const count = todosStore.query(getEntitiesCount());
```

### `getEntitiesCountByPredicate`

Get the store's entity collection size:

```ts
import { getEntitiesCountByPredicate } from '@ngneat/elf-entities';

const count = todosStore.query(
  getEntitiesCountByPredicate((entity) => entity.completed),
);
```

## Mutations

### `setEntities`

Replace current collection with the provided collection:

```ts
import { setEntities } from '@ngneat/elf-entities';

todosStore.update(setEntities([todo, todo]));
```

### `setEntitiesMap`

Replace current collection with the provided map:

```ts
import { setEntitiesMap } from '@ngneat/elf-entities';

const todos = {
  1: {
    id: 1,
    task: 'Buy milk',
  },
  2: {
    id: 2,
    task: 'Fix car',
  },
};
todosStore.update(setEntitiesMap(todos));
```

### `addEntities`

Add an entity or entities to the store:

```ts
import { addEntities } from '@ngneat/elf-entities';

todosStore.update(addEntities(todo));

todosStore.update(addEntities([todo, todo]));

todosStore.update(addEntities([todo, todo], { prepend: true }));
```

### `addEntitiesFifo`

Add an entity or entities to the store using fifo strategy:

```ts
import { addEntitiesFifo } from '@ngneat/elf-entities';

todosStore.update(addEntitiesFifo([entity, entity]), { limit: 3 });
```

### `updateEntities`

Update an entity or entities in the store:

```ts
import { updateEntities } from '@ngneat/elf-entities';

todosStore.update(updateEntities(id, { name }));

todosStore.update(updateEntities(id, (entity) => ({ ...entity, name })));

todosStore.update(updateEntities([id, id, id], { open: true }));
```

### `updateEntitiesByPredicate`

Update an entity or entities in the store:

```ts
import { updateEntitiesByPredicate } from '@ngneat/elf-entities';

todosStore.update(
  updateEntitiesByPredicate(({ count }) => count === 0, { open: false }),
);

todosStore.update(
  updateEntitiesByPredicate(({ count }) => count === 0),
  (entity) => ({ ...entity, open: false }),
);
```

### `updateAllEntities`

Update all entities in the store:

```ts
import { updateAllEntities } from '@ngneat/elf-entities';

todosStore.update(updateAllEntities({ name: 'elf' }));

todosStore.update(
  updateAllEntities((entity) => ({ ...entity, count: entity.count + 1 })),
);
```

### `upsertEntities`

Add or update entities.

To identify entities in the store, every entity must have an `id` property. Any partial entities will be merged with the existing ones:

```ts
import { upsertEntitiesBy } from '@ngneat/elf-entities';

todosStore.update(upsertEntities({ id: '1', happy: true }));

todosStore.update(
  upsertEntities([
    { id: '1', happy: true },
    { id: '2', name: 'elf 2', happy: false },
  ]),
);
```

### `upsertEntitiesById`

Insert or update an entity. When the id isn't found, it creates a new entity; otherwise, it performs an update:

```ts
import { upsertEntitiesById } from '@ngneat/elf-entities';

const creator = (id) => createTodo(id);

todosStore.update(
  upsertEntitiesById(1, {
    updater: { name: 'elf' },
    creator,
  }),
);

todosStore.update(
  upsertEntitiesById([1, 2], {
    updater: (entity) => ({ ...entity, count: entity.count + 1 }),
    creator,
  }),
);
```

To perform a merge between a new entity and an `updater` result, use the `mergeUpdaterWithCreator` option:

```ts
todosStore.update(
  upsertEntitiesById([1, 2], {
    updater: (entity) => ({ ...entity, name }),
    creator,
    // highlight-next-line
    mergeUpdaterWithCreator: true,
  }),
);
```

The above example will first create the entity using the _creator_ method, then pass the result to the _updater_ method, and merge both.

### `updateEntitiesIds`

Update id of an entity or entities in the store:

```ts
import { updateEntitiesIds } from '@ngneat/elf-entities';

todosStore.update(updateEntitiesIds(oldId, newId));

todosStore.update(updateEntitiesIds([oldId1, oldId2], [newId1, newId2]));
```

The most common use case for this is "optimistic updates":

```ts
function addTodo(todo: Todo) {
  const tempId = generateRandomId();
  todosStore.update(addEntities({ ...todo, id: tempId }));

  addTodoToServer(todo).then(
    (response) => {
      todosStore.update(
        updateEntitiesIds(tempId, response.id),
        updateEntities(response.id, response),
      );
    },
    (error) => {
      todosStore.update(deleteEntities(tempId));
      // handle error
    },
  );
}
```

### `deleteEntities`

Delete an entity or entities from the store:

```ts
import { deleteEntities } from '@ngneat/elf-entities';

todosStore.update(deleteEntities(id));
todosStore.update(deleteEntities([id, id]));
```

### `deleteEntitiesByPredicate`

Delete an entity or entities from the store:

```ts
import { deleteEntitiesByPredicate } from '@ngneat/elf-entities';

todosStore.update(deleteEntitiesByPredicate(({ completed }) => completed));
```

### `deleteAllEntities`

Delete all entities from the store:

```ts
import { deleteAllEntities } from '@ngneat/elf-entities';

todosStore.update(deleteAllEntities());
```

### `moveEntity`

Moves an entity within the store:

```ts
import { moveEntity } from '@ngneat/elf-entities';

todosStore.update(moveEntity({ fromIndex: 0, toIndex: 1 }));
```

## idKey

By default, Elf takes the `id` key from the entity `id` field. To change it, you can pass the `idKey` option to
the `withEntities` function:

```ts
import { createStore } from '@ngneat/elf';
import { addEntities } from '@ngneat/elf-entities';

interface Todo {
  _id: number;
  label: string;
}

const todosStore = createStore(
  { name: 'todos' },
  withEntities<Todo, '_id'>({ idKey: '_id' }),
);
```

## initialValue

In case that you need to start the `entities` state with a value, you can specify it in the `initialValue` configuration:

```ts
import { createStore } from '@ngneat/elf';

const store = createStore(
  { name },
  withEntities<Widget>({ initialValue: [{ id: 1, name: '' }] }),
);
```
